package ch.ethz.fcl.metrobuzz.render.accessbility.view.od;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.MBContainer;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.PathPublic;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.PathPublic.Route;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.SGMRT;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.Walk;
import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.accessibility.cost.MBEdgeCost;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Edge.EdgeType;
import ch.ethz.fcl.metrobuzz.data.singapore.SGMRTStations;
import ch.ethz.fcl.metrobuzz.render.accessbility.AccessibilityRender;
import ch.ethz.fcl.metrobuzz.render.accessbility.interaction.AcceMouseAction;
import ch.ethz.fcl.metrobuzz.render.accessbility.view.local.FlowMapView;
import ch.ethz.fcl.metrobuzz.render.techniques.arrow.Arrow;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorTable;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.SGMRTColorTable;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.LineSegment;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.tools.MBGL;
import ch.ethz.fcl.metrobuzz.tools.MBTools;
import ch.ethz.fcl.metrofont.Pen;

public class ODTemporalChangeView {
	private MBContainer container;

	private AccessibilityRender acceRender;
	private AcceMouseAction mouseAct;
	private FlowMapView flowMapView;

	private Map<LineSegment, List<Edge>> groupLinkMap;

	public ODTemporalChangeView(MBContainer container,
			AccessibilityRender acceRender) {
		this.container = container;
		this.acceRender = acceRender;
		this.mouseAct = this.acceRender.getMouseAction();
		this.flowMapView = this.acceRender.getFlowMapView();
	}

	public void render() {
		if (mouseAct.getOrigin() == null || mouseAct.getDestination() == null)
			return;

		float lineWidth = container.getViewer().getProjection()
				.getWorldDistance(20);

		setStenci(lineWidth);
		renderOutline(lineWidth);
		GL11.glDisable(GL11.GL_STENCIL_TEST);

		renderTotalCost(mouseAct.getDestination().toPoint());

		renderODPath(lineWidth);
		// fillGapBetweenRoute(lineWidth);

		flowMapView.renderODStop(mouseAct.getOrigin());
		flowMapView.renderODStop(mouseAct.getDestination());

		groupLinks();
		// debugLinkGroup();
		renderTemporalChange();
	}

	// render the path from source to destination stop
	private void renderODPath(float lineWidth) {
		PathPublic p = flowMapView.getPathMap().get(mouseAct.getDestination());

		if (p == null) {
			return;
		}

		List<Route> routes = p.getPath();
		List<List<Node>> exactPath = p.getExactPath();

		for (int routeIndex = 0; routeIndex < routes.size(); routeIndex++) {
			List<Node> onePath = exactPath.get(routeIndex);
			Route route = routes.get(routeIndex);

			for (int k = 0; k < onePath.size() - 1; k++) {
				Point2D p1 = onePath.get(k).toPoint();
				Point2D p2 = onePath.get(k + 1).toPoint();

				assignColor(routeIndex, route);

				MBGL.render(p1, lineWidth, p2, lineWidth, false, false);
			}

			if (routes.size() > 1 && routeIndex > 0) {
				Route r2 = routes.get(routeIndex - 1);
				List<Node> nList2 = exactPath.get(routeIndex - 1);

				fillGapBetweenRoute(lineWidth, routeIndex - 1, r2, nList2,
						routeIndex, route, onePath);
			}

			if (onePath.size() > 2) {
				for (int k = 0; k < onePath.size() - 2; k++) {
					Point2D p1 = onePath.get(k).toPoint();
					Point2D p2 = onePath.get(k + 1).toPoint();
					Point2D p3 = onePath.get(k + 2).toPoint();

					fillGapWithinRoute(routeIndex, route, lineWidth, p1, p2, p3);
				}
			}

			if (route.getTransitRoute() != null) {
				Node node = route.getBegin();

				Point2D loc = node.toPoint();

				if (loc == null)
					continue;

//				 MBColorTable.assignColor(MBColorTable.PURPLE, 1.0f);
				assignColor(routeIndex, route);

				container.getFont().display(
						route.getTransitRoute().getLine().getId(),
						new Pen(loc.getX() + lineWidth, loc.getY() - lineWidth / 2,
								container.getViewer().getProjection()
										.getWorldDistance(25)));
			}
		}
	}

	private void renderOutline(float lineWidth) {
		PathPublic p = flowMapView.getPathMap().get(mouseAct.getDestination());

		if (p == null) {
			return;
		}

		List<Route> routes = p.getPath();
		List<List<Node>> exactPath = p.getExactPath();

		GL11.glLineWidth(2.0f);
		GL11.glColor4f(0, 0, 0, 1);
		GL11.glBegin(GL11.GL_LINES);
		for (int routeIndex = 0; routeIndex < routes.size(); routeIndex++) {
			List<Node> onePath = exactPath.get(routeIndex);

			for (int k = 0; k < onePath.size() - 1; k++) {
				Point2D p1 = onePath.get(k).toPoint();
				Point2D p2 = onePath.get(k + 1).toPoint();

				Vector2d dir1 = new Vector2d(p2.getX() - p1.getX(), p2.getY()
						- p1.getY());
				Vector2d verDir1 = MBTools.rotate(dir1, Math.PI / 2);

				Point2D p11 = p1.getPointAt(verDir1, lineWidth / 2);
				Point2D p12 = p1.getPointAt(verDir1, -lineWidth / 2);

				Point2D p21 = p2.getPointAt(verDir1, lineWidth / 2);
				Point2D p22 = p2.getPointAt(verDir1, -lineWidth / 2);

				MBGL.render(p11);
				MBGL.render(p21);

				MBGL.render(p12);
				MBGL.render(p22);

				if (onePath.size() > 2 && k < onePath.size() - 2) {
					Point2D p3 = onePath.get(k + 2).toPoint();

					Vector2d dir2 = p2.getDir(p3);

					double angle = MBTools.angle(dir1, dir2);

					float thre = 0.00001f;

					// two lines are parallel
					if (Math.abs(angle) < thre
							|| Math.PI - Math.abs(angle) < thre)
						continue;
					else {
						Point2D gap_p1, gap_p2;
						if (angle > 0) {
							gap_p1 = p22;
							gap_p2 = p2.getPointAt(
									MBTools.rotate(dir2, Math.PI / 2),
									-lineWidth / 2);
						} else {
							gap_p1 = p21;
							gap_p2 = p2.getPointAt(
									MBTools.rotate(dir2, Math.PI / 2),
									lineWidth / 2);
						}

						gap_p1.render();
						gap_p2.render();
					}
				}
			}
		}
		GL11.glEnd();
	}

	private void setStenci(float lineWidth) {
		GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
		GL11.glEnable(GL11.GL_STENCIL_TEST);

		GL11.glColorMask(false, false, false, false);

		GL11.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
		GL11.glStencilOp(GL11.GL_REPLACE, GL11.GL_REPLACE, GL11.GL_REPLACE);

		// set paintable for whole screen
		GL11.glBegin(GL11.GL_POLYGON);
		GL11.glVertex3f(-1, -1, 0);
		GL11.glVertex3f(-1, 1, 0);
		GL11.glVertex3f(1, 1, 0);
		GL11.glVertex3f(1, -1, 0);
		GL11.glEnd();

		GL11.glStencilFunc(GL11.GL_ALWAYS, 0, 0);
		GL11.glStencilOp(GL11.GL_REPLACE, GL11.GL_REPLACE, GL11.GL_REPLACE);

		// set not paintable along the route
		renderODPath(lineWidth);

		// render setting
		GL11.glColorMask(true, true, true, true);
		GL11.glStencilFunc(GL11.GL_EQUAL, 1, 1);
		GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
	}

	private void groupLinks() {
		PathPublic p = flowMapView.getPathMap().get(mouseAct.getDestination());

		if (p == null) {
			return;
		}

		List<Route> routes = p.getPath();
		List<List<Node>> exactPath = p.getExactPath();

		groupLinkMap = new LinkedHashMap<LineSegment, List<Edge>>();

		// maximum distance is 3 pixels
		float maxDist = container.getViewer().getProjection()
				.getWorldDistance(3);

		// maximum angle 60;
		double maxAngle = Math.PI / 6;

		for (int routeIndex = 0; routeIndex < routes.size(); routeIndex++) {
			List<Node> onePath = exactPath.get(routeIndex);
			Route route = routes.get(routeIndex);
			if (route.getTransitRoute() == null)
				continue;

			String lineId = route.getTransitRoute().getLine().getId();

			if (SGMRTStations.isMRTStation(lineId))
				continue;

			if (onePath.size() < 3)
				continue;

			for (int n = 0; n < onePath.size() - 2; n++) {
				int groupCount = 0;

				Point2D pStart = onePath.get(n).toPoint();
				Point2D pMid, pEnd;

				LineSegment ls = null;
				float dist = 0;
				double angle = 0;

				while (dist <= maxDist && angle <= maxAngle
						&& n + 2 + groupCount < onePath.size()) {
					pEnd = onePath.get(n + 2 + groupCount).toPoint();

					ls = new LineSegment(pStart, pEnd);
					for (int i = n + 1; i < n + 2 + groupCount; i++) {
						pMid = onePath.get(i).toPoint();
						dist = Math.max(dist, ls.distance(pMid));

						Point2D pPre = onePath.get(i - 1).toPoint();
						Point2D pNext = onePath.get(i + 1).toPoint();

						Vector2d dir1 = pPre.getDir(pMid), dir2 = pMid
								.getDir(pNext);
						angle = Math.max(angle,
								Math.abs(MBTools.angle(dir1, dir2)));
					}

					groupCount++;
				}

				pEnd = onePath.get(n + groupCount).toPoint();
				ls = new LineSegment(pStart, pEnd);

				List<Edge> groupEdge = new ArrayList<Edge>();
				for (int i = n; i < n + groupCount; i++) {
					Edge e = container.getData().getScene()
							.getEdge(onePath.get(i), onePath.get(i + 1));
					groupEdge.add(e);
				}

				groupLinkMap.put(ls, groupEdge);
				n += (groupCount - 1);
			}
		}
	}

	private void renderTemporalChange() {
		float lineWidth = container.getViewer().getProjection()
				.getWorldDistance(20);

		PathPublic p = flowMapView.getPathMap().get(mouseAct.getDestination());

		if (p == null) {
			return;
		}

		List<LineSegment> lsList = new ArrayList<LineSegment>(
				groupLinkMap.keySet());

		List<float[]> costList = new ArrayList<float[]>();

		float maxCost = 0;

		for (int i = 0; i < lsList.size(); i++) {
			LineSegment ls = lsList.get(i);

			float[] costs = new float[MBData.ONE_DAY_TIME];

			for (int t = 0; t < (MBData.ONE_DAY_TIME - MBData.TRIP_STRAT_TIME); t++) {
				costs[t + MBData.TRIP_STRAT_TIME - 1] = 0;
				for (Edge e : groupLinkMap.get(ls)) {
					MBEdgeCost edgeCost = container.getData().getCost()
							.getEdgeCost(e);
					if (edgeCost == null)
						continue;

					costs[t + MBData.TRIP_STRAT_TIME - 1] += edgeCost.getCost(t
							+ MBData.TRIP_STRAT_TIME - 1);
				}
				maxCost = Math.max(maxCost, costs[t + MBData.TRIP_STRAT_TIME
						- 1]);
			}

			costList.add(costs);
		}

		for (int i = 0; i < lsList.size(); i++) {
			LineSegment ls = lsList.get(i);

			Point2D p1 = ls.getPoints()[0], p2 = ls.getPoints()[1];

			Vector2d dir = p1.getDir(p2);

			p2 = p2.getPointAt(dir, -lineWidth / 20 * 4);
			float lsDist = p1.distance(p2) - lineWidth / 20 * 12f;

			if (lsDist >= lineWidth * 4) {
				lsDist = lineWidth * 4;

				Point2D pMid = p1.getPointAt(p2, 0.5f);
				p1 = pMid.getPointAt(dir, -lsDist / 2);
				p2 = pMid.getPointAt(dir, lsDist / 2 + lineWidth / 20 * 12f);
			}
			Vector2d verDir = MBTools.rotate(dir, Math.PI / 2);

			if (lsDist > lineWidth * 1.5f) {
				MBColorTable.assignColor(MBColorTable.YELLOW, 1.0f);
				Arrow.render(container, p2, p1);

				GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
				float distGap = lsDist
						/ (MBData.ONE_DAY_TIME - MBData.TRIP_STRAT_TIME);

				for (int t = 0; t < (MBData.ONE_DAY_TIME - MBData.TRIP_STRAT_TIME); t++) {
					Point2D pt = p1.getPointAt(dir, distGap * t);

					float shiftLength = costList.get(i)[t
							+ MBData.TRIP_STRAT_TIME - 1]
							/ maxCost * lineWidth / 2.1f;

					Point2D pt1 = pt.getPointAt(verDir, shiftLength);
					Point2D pt2 = pt.getPointAt(verDir, -shiftLength);

					MBGL.render(pt1);
					MBGL.render(pt2);
				}
				GL11.glEnd();
			}
		}

		// List<Route> routes = p.getPath();
		// List<List<Node>> exactPath = p.getExactPath();
		// for (int routeIndex = 0; routeIndex < routes.size(); routeIndex++) {
		// List<Node> onePath = exactPath.get(routeIndex);
		// Route route = routes.get(routeIndex);
		// if (route.getTransitRoute() == null)
		// continue;
		//
		// for (int k = 0; k < onePath.size() - 1; k++) {
		// Edge e = container.getData().getScene()
		// .getEdge(onePath.get(k), onePath.get(k + 1));
		// MBEdgeCost edgeCost = container.getData().getCost()
		// .getEdgeCost(e);
		// String lineId = route.getTransitRoute().getLine().getId();
		//
		// if (edgeCost != null) { // is bus
		// for (int t = 0; t < (MBData.ONE_DAY_TIME - MBData.TRIP_STRAT_TIME);
		// t++)
		// maxCost = Math.max(maxCost,
		// edgeCost.getCost(t + MBData.TRIP_STRAT_TIME));
		// } else if (SGMRTStations.isMRTStation(lineId)) {
		// maxCost = Math.max(SGMRT.mrtCost(container.getData(),
		// lineId, onePath.get(k).toPoint(), onePath
		// .get(k + 1).toPoint()), maxCost);
		// }
		// }
		//
		// }
		//
		// for (int routeIndex = 0; routeIndex < routes.size(); routeIndex++) {
		// List<Node> onePath = exactPath.get(routeIndex);
		// Route route = routes.get(routeIndex);
		//
		// if (route.getTransitRoute() == null)
		// continue;
		//
		// GL11.glLineWidth(1.0f);
		// for (int k = 0; k < onePath.size() - 1; k++) {
		// Edge e = container.getData().getScene()
		// .getEdge(onePath.get(k), onePath.get(k + 1));
		//
		// Point2D p1 = onePath.get(k).toPoint();
		// Point2D p2 = onePath.get(k + 1).toPoint();
		// Vector2d dir = p1.getDir(p2);
		// float distance = p1.distance(p2) - lineWidth / 20 * 12f;
		//
		// if (distance >= lineWidth * 4) {
		// distance = lineWidth * 4;
		//
		// Point2D pMid = p1.getPointAt(p2, 0.5f);
		// p1 = pMid.getPointAt(dir, -distance / 2);
		// p2 = pMid.getPointAt(dir, distance / 2 + lineWidth / 20
		// * 12f);
		// }
		//
		// Vector2d verDir = MBTools.rotate(dir, Math.PI / 2);
		// if (distance > lineWidth * 2) {
		// MBColorTable.assignColor(MBColorTable.BLUE, 1.0f);
		//
		// Arrow.render(container, p2, p1);
		//
		// GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
		// float distGap = distance
		// / (MBData.ONE_DAY_TIME - MBData.TRIP_STRAT_TIME);
		//
		// MBEdgeCost edgeCost = container.getData().getCost()
		// .getEdgeCost(e);
		//
		// String lineId = route.getTransitRoute().getLine().getId();
		// for (int t = 0; t < (MBData.ONE_DAY_TIME - MBData.TRIP_STRAT_TIME);
		// t++) {
		// Point2D pt = p1.getPointAt(dir, distGap * t);
		//
		// float shiftLength = 0;
		//
		// if (edgeCost != null) { // is bus
		// shiftLength = edgeCost.getCost(t
		// + MBData.TRIP_STRAT_TIME)
		// / maxCost * lineWidth / 2.1f;
		//
		// } else if (SGMRTStations.isMRTStation(lineId)) {
		// shiftLength = SGMRT.mrtCost(container.getData(),
		// lineId, onePath.get(k).toPoint(), onePath
		// .get(k + 1).toPoint())
		// / maxCost * lineWidth / 2.1f;
		// }
		//
		// Point2D pt1 = pt.getPointAt(verDir, shiftLength);
		// Point2D pt2 = pt.getPointAt(verDir, -shiftLength);
		//
		// MBGL.render(pt1);
		// MBGL.render(pt2);
		// }
		// GL11.glEnd();
		// }
		// }
		// }
	}

	// render total cost at the destination
	private void renderTotalCost(Point2D dest) {
		float radius = container.getViewer().getProjection()
				.getWorldDistance(150);

		List<float[]> costList = new ArrayList<float[]>();

		PathPublic p = flowMapView.getPathMap().get(mouseAct.getDestination());

		if (p == null) {
			return;
		}

		List<Route> routes = p.getPath();
		List<List<Node>> exactPath = p.getExactPath();

		float maxCost = 0;
		float[] totalCost = new float[MBData.ONE_DAY_TIME];

		for (int routeIndex = 0; routeIndex < routes.size(); routeIndex++) {
			List<Node> onePath = exactPath.get(routeIndex);
			Route route = routes.get(routeIndex);

			float[] cost = new float[MBData.ONE_DAY_TIME];
			// walk
			if (route.getTransitRoute() == null) {
				if (routeIndex == routes.size() - 1)
					break;

				for (int t = MBData.TRIP_STRAT_TIME; t <= MBData.ONE_DAY_TIME; t++)
					cost[t - 1] += Walk.walkCost(container.getData(), onePath
							.get(0).toPoint(), onePath.get(1).toPoint());

				onePath = exactPath.get(routeIndex + 1);
				route = routes.get(routeIndex + 1);
				routeIndex++;
			}

			for (int t = MBData.TRIP_STRAT_TIME; t <= MBData.ONE_DAY_TIME; t++) {
				for (int k = 0; k < onePath.size() - 1; k++) {
					Edge e = container.getData().getScene()
							.getEdge(onePath.get(k), onePath.get(k + 1));
					MBEdgeCost edgeCost = container.getData().getCost()
							.getEdgeCost(e);

					if (edgeCost == null) {
						if (SGMRTStations.isMRTStation(e.getId())) {
							float mrtCost = SGMRT.mrtCost(container.getData(),
									e.getId(), e.getStartNode().toPoint(), e
											.getEndNode().toPoint());
							cost[t - 1] += mrtCost;

							if (k == 0) {
								// morning and evening peak hour, wait 1 minute
								if ((t >= 30 && t <= 34)
										|| (t >= 70 && t <= 76))
									cost[t - 1] += 60;
								// after 11pm, wait 5 minutes
								else if (t >= 90)
									cost[t - 1] += 300;
								// non peak hour, wait 2 minutes
								else
									cost[t - 1] += 120;
							}
							// System.out.println("Edge " + e.getId() + " cost "
							// + mrtCost);
						} else
							continue;
					} else {
						// after 10:30 pm, wait 10 minutes
						if (t >= 88 && k == 0)
							cost[t - 1] += 600;

						cost[t - 1] += edgeCost.getCost(t);
					}
				}

				totalCost[t - 1] += cost[t - 1];
				maxCost = Math.max(maxCost, totalCost[t - 1]);
			}

			costList.add(cost);
		}

		// System.out.println("list size "+costList.size());

		Vector2d ydir = new Vector2d(0, 1);
		double angleGap = Math.PI * 2 / 96;

		float startRatio = 2.4f, endRatio = 3.7f, totalRatio = 4.0f;

		for (int i = 1; i <= 96; i++) {
			double angleRotate = angleGap * i;

			Vector2d dir = MBTools.rotate(ydir, -angleRotate);
			Point2D point = dest.getPointAt(dir, radius);

			if (i % 4 == 0) {
				String str = i / 4 + "";
				float fontSize = container.getViewer().getProjection()
						.getWorldDistance(12);
				if (i % 24 == 0) {
					fontSize = container.getViewer().getProjection()
							.getWorldDistance(20);

				}

				float w = container.getFont().getLength(str, fontSize);
				float h = fontSize * 1.2f;

				float x = (float) (point.getX() - w / 2 + w / 2
						* Math.sin(angleRotate));
				float y = (float) (point.getY() - h / 2
						* Math.sin(angleRotate / 2));

				MBColorTable.assignColor(MBColorTable.BLACK, 1.0f);
				container.getFont()
						.display(i / 4 + "", new Pen(x, y, fontSize));
			}

			if (i >= 24 && i < 94) {
				int skip = 0;
				Point2D start = dest.getPointAt(dir, radius * startRatio
						/ totalRatio);
				Point2D end = start;

				float width1 = (float) (radius * startRatio / totalRatio * Math
						.sin(Math.PI / 96)) * 2f;
				float width2 = width1;

				for (int j = 0; j < routes.size(); j++) {
					Route route = routes.get(j);
					if (route.getTransitRoute() == null) {
						skip++;
						continue;
					}
					float[] cost = costList.get(j - skip);
					float length = (cost[i - 1] / maxCost) * radius
							* (endRatio - startRatio) / totalRatio;
					end = start.getPointAt(dir, length);

					width2 = (float) (dest.distance(end) * Math
							.sin(Math.PI / 96)) * 2f;

					assignColor(j, route);
					MBGL.render(start, width1, end, width2, true, true);

					start = end;
					width1 = width2;
				}
			} else {
				Point2D start = dest.getPointAt(dir, radius * startRatio
						/ totalRatio);
				Point2D end = dest.getPointAt(dir, radius
						* (startRatio + (endRatio - startRatio) / 3)
						/ totalRatio);

				float width1 = (float) (radius * startRatio / totalRatio * Math
						.sin(Math.PI / 96)) * 2f;
				float width2 = (float) (radius
						* (startRatio + (endRatio - startRatio) / 3)
						/ totalRatio * Math.sin(Math.PI / 96)) * 2f;

				MBColorTable.assignColor(MBColorTable.GREY, 0.8f);
				MBGL.render(start, width1, end, width2, false, true);
			}
		}

		Vector2d dir = MBTools.rotate(ydir, -angleGap * MBData.startTime);
		float rotate = 0;

		float cRadius = radius * (endRatio + 0.1f) / totalRatio;
		Point2D startP = dest.getPointAt(dir, cRadius);

		Point2D endP = startP;
		int skip = 0;

		float boundWidth = container.getViewer().getProjection()
				.getWorldDistance(4);

		Point2D startP1 = startP.getPointAt(dir, -boundWidth / 2);
		Point2D startP2 = startP.getPointAt(dir, boundWidth / 2);

		GL11.glColor4f(0.1f, 0.1f, 0.1f, 0.8f);
		GL11.glLineWidth(1.0f);

		// render a line at the begininng
		GL11.glBegin(GL11.GL_LINES);
		startP1.render();
		startP2.render();
		GL11.glEnd();

		for (int j = 0; j < routes.size(); j++) {
			Route route = routes.get(j);
			if (route.getTransitRoute() == null) {
				skip++;
				continue;
			}
			float[] cost = costList.get(j - skip);
			rotate = (float) (cost[MBData.startTime - 1] / (60 * 60 * 24)
					* Math.PI * 2);
			// rotate = (float) ( Math.PI / 6);

			int slices = (int) (rotate / (Math.PI * 2) * 100);
			for (int i = 0; i <= slices; i++) {
				dir = MBTools.rotate(dir, -(Math.PI * 2) / 100);
				endP = dest.getPointAt(dir, cRadius);

				assignColor(j, route);
				MBGL.render(startP, boundWidth, endP, boundWidth, true, false);

				startP = endP;
			}
		}

		startP1 = startP.getPointAt(dir, -boundWidth / 2);
		startP2 = startP.getPointAt(dir, boundWidth / 2);
		// render a line at the end
		GL11.glBegin(GL11.GL_LINES);
		startP1.render();
		startP2.render();
		GL11.glEnd();

	}

	private void assignColor(int index, Route route) {
		if (route.getTransitRoute() == null)
			MBColorTable.assignColor(MBColorTable.GREY, 1.0f);
		else {
			String id = route.getTransitRoute().getLine().getId();
			if (id.startsWith("EW"))
				SGMRTColorTable.assignSubwayColor(EdgeType.EW, 1.0f);
			else if (id.startsWith("NS"))
				SGMRTColorTable.assignSubwayColor(EdgeType.NS, 1.0f);
			else if (id.startsWith("CC"))
				SGMRTColorTable.assignSubwayColor(EdgeType.CC, 1.0f);
			else if (id.startsWith("CG"))
				SGMRTColorTable.assignSubwayColor(EdgeType.EW, 1.0f);
			else if (id.startsWith("NE"))
				SGMRTColorTable.assignSubwayColor(EdgeType.NE, 1.0f);
			else
				MBColorTable.assignColor(index, 1.0f);
		}
	}

	private void fillGapWithinRoute(int index, Route route, float width,
			Point2D pStart, Point2D pMid, Point2D pEnd) {
		Point2D intersect = pMid;

		if (intersect == null)
			return;

		Vector2d dir1 = pStart.getDir(pMid), dir2 = pMid.getDir(pEnd);

		double angle = MBTools.angle(dir1, dir2);

		float thre = 0.00001f;

		// two lines are parallel
		if (Math.abs(angle) < thre || Math.PI - Math.abs(angle) < thre)
			return;
		else {
			Point2D p1, p2;
			if (angle > 0) {
				p1 = intersect.getPointAt(MBTools.rotate(dir1, Math.PI / 2),
						-width / 2);
				p2 = intersect.getPointAt(MBTools.rotate(dir2, Math.PI / 2),
						-width / 2);
			} else {
				p1 = intersect.getPointAt(MBTools.rotate(dir1, Math.PI / 2),
						width / 2);
				p2 = intersect.getPointAt(MBTools.rotate(dir2, Math.PI / 2),
						width / 2);
			}

			assignColor(index, route);

			GL11.glBegin(GL11.GL_TRIANGLES);
			intersect.render();
			p1.render();
			p2.render();
			GL11.glEnd();

			// GL11.glColor4f(0.1f, 0.1f, 0.1f, 0.8f);
			// GL11.glLineWidth(1.0f);
			// GL11.glBegin(GL11.GL_LINES);
			// p1.render();
			// p2.render();
			// GL11.glEnd();
		}
	}

	private void fillGapBetweenRoute(float width, int index1, Route route1,
			List<Node> nodeList1, int index2, Route route2, List<Node> nodeList2) {
		Point2D pStart = nodeList1.get(nodeList1.size() - 2).toPoint();
		Point2D pEnd = nodeList2.get(1).toPoint();
		Point2D intersect = nodeList1.get(nodeList1.size() - 1).toPoint();

		if (intersect == null)
			return;

		Vector2d dir1 = pStart.getDir(intersect), dir2 = intersect.getDir(pEnd);

		double angle = MBTools.angle(dir1, dir2);

		float thre = 0.00001f;

		// two lines are parallel
		if (Math.abs(angle) < thre || Math.PI - Math.abs(angle) < thre)
			return;
		else {
			Point2D p1, p2;
			if (angle > 0) {
				p1 = intersect.getPointAt(MBTools.rotate(dir1, Math.PI / 2),
						-width / 2);
				p2 = intersect.getPointAt(MBTools.rotate(dir2, Math.PI / 2),
						-width / 2);
			} else {
				p1 = intersect.getPointAt(MBTools.rotate(dir1, Math.PI / 2),
						width / 2);
				p2 = intersect.getPointAt(MBTools.rotate(dir2, Math.PI / 2),
						width / 2);
			}
			Point2D p12Mid = p1.getPointAt(p2, 0.5f);

			// fill gap 1
			assignColor(index1, route1);
			GL11.glBegin(GL11.GL_TRIANGLES);
			intersect.render();
			p1.render();
			p12Mid.render();
			GL11.glEnd();

			// fill gap 2
			assignColor(index2, route2);
			GL11.glBegin(GL11.GL_TRIANGLES);
			intersect.render();
			p2.render();
			p12Mid.render();
			GL11.glEnd();

			// render outline
			GL11.glColor4f(0.1f, 0.1f, 0.1f, 1.0f);
			GL11.glLineWidth(1.0f);
			GL11.glBegin(GL11.GL_LINES);
			p1.render();
			p2.render();
			GL11.glEnd();

			Point2D pStart3, p3, p4, pEnd4;
			if (angle > 0) {
				p3 = intersect.getPointAt(MBTools.rotate(dir1, Math.PI / 2),
						width / 2);
				pStart3 = pStart.getPointAt(MBTools.rotate(dir1, Math.PI / 2),
						width / 2);
				p4 = intersect.getPointAt(MBTools.rotate(dir2, Math.PI / 2),
						width / 2);
				pEnd4 = pEnd.getPointAt(MBTools.rotate(dir2, Math.PI / 2),
						width / 2);
			} else {
				p3 = intersect.getPointAt(MBTools.rotate(dir1, Math.PI / 2),
						-width / 2);
				pStart3 = pStart.getPointAt(MBTools.rotate(dir1, Math.PI / 2),
						-width / 2);
				p4 = intersect.getPointAt(MBTools.rotate(dir2, Math.PI / 2),
						-width / 2);
				pEnd4 = pEnd.getPointAt(MBTools.rotate(dir2, Math.PI / 2),
						-width / 2);
			}
			LineSegment line1 = new LineSegment(pStart3, p3);
			LineSegment line2 = new LineSegment(p4, pEnd4);

			Point2D p34Intersect = line1.intersects(line2);

			if (p34Intersect == null)
				return;

			// fill overlap 1
			assignColor(index1, route1);
			GL11.glBegin(GL11.GL_TRIANGLES);
			intersect.render();
			p4.render();
			p34Intersect.render();
			GL11.glEnd();

			// fill overlap 2
			assignColor(index2, route2);
			GL11.glBegin(GL11.GL_TRIANGLES);
			intersect.render();
			p3.render();
			p34Intersect.render();
			GL11.glEnd();
		}
	}
}
